<?php
/*******************************************************************************

    FinalsClub.org is a platform for students and faculty to collaborate
    and share their knowledge with the world.

    Copyright (C) 2009  Andrew Magliozzi

    This file is a part of FinalsClub.org.

    FinalsClub.org is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    To contact FinalsClub.org, send an email to info@finalsclub.org, or
    send mail to Finals Club, c/o Veritas Tutors, 1132 Massachusetts Avenue,
    Cambridge, MA 02138.

*******************************************************************************/

/**
 * password_reset actions.
 *
 * @package    FinalsClub
 * @subpackage password_reset
 * @author     Tom O'Neill
 * @version    SVN: $Id: actions.class.php 12474 2008-10-31 10:41:27Z fabien $
 */
class password_resetsActions extends sfActions
{

 /**
  * Presents a form for the user to enter the e-mail address their account is registered to
  * posts to create action
  * 
  * @param $request
  * @return sfView::SUCCESS
  */
  public function executeNew($request)
  {
    $this->form = new PasswordResetForm();
  }

 /**
  * Validates the e-mail address entered by the user and checks for a valid account
  * if a valid account is found the action checks for a pre-existing reset request
  * object in the database. If no such record is found a new one is generated. The
  * object is assigned a new random token and a 24 hour expiration period.
  * A url is finally generated with the request id and token in it which is e-mailed
  * to the user and directs them to the reset action.
  * 
  * @param $request
  * @return sfView::SUCCESS
  */
  public function executeCreate($request)
  {
  	// this action handles the submit of the form presented by the 'new' action
  	// if this request isnt a post redirect to 404
  	$this->forward404Unless($request->isMethod('post'));
    $this->user = new User();
    $this->form = new PasswordResetForm();
    
    // bind form values
    $this->form->bind($request->getParameter('password_reset'));
    
    // the validators for this form check for two things:
    // a valid e-mail address format and an account registered to the e-mail
    if($this->form->isValid())	{	
    	$email_address = $request->getParameter('password_reset[email]');

    	// lookup the user account registered to $email_address
    	$user = new User();
    	$user = UserPeer::retrieveByEmail($email_address);
    	
    	// check for a password reset record in the database belonging to the user
		$reset_request = PasswordResetPeer::retrieveByUserId($user->getId());
    	if(!($reset_request instanceOf PasswordReset)) {	// no entry was found
			$reset_request = new PasswordReset();			// we'll generate a new one
		}
		
		// our token is the 11 characters after the first four of a md5 hash of the current unix timestamp
    	$token = substr(md5(time()),4,11);
		$utime = time() + 86400; // unix timestamp for now plus 24 hours (86400)
		$expires = date('Y-m-d G:i:s', $utime);		// formatted timestamp (ex : 1990-09-14 12:45:52)

		$reset_request->setToken($token);			// set the token
		$reset_request->setUserId($user->getId());	// set the user_id
		$reset_request->setExpiredAt($expires);		// set the expiration
		
		$reset_request->save();		// save the record
		
		// generate the full url for the route 'reset_password' with the request_id and token as parameters
		// this will be passed to our HTML and PlainText e-mail body partials 
    	$reset_url = $this->generateUrl('reset_password', array('id'=>$reset_request->getId(), 'token'=>$reset_request->getToken()), true);
		
    	// retrieve the smtp server from the app.yml
		$smtp_server = sfConfig::get('app_smtp_server');
		$smtp_user = sfConfig::get('app_smtp_user');
		$smtp_password = sfConfig::get('app_smtp_password');
		try
		{
		  // Create the mailer
		  $connection = new Swift_Connection_SMTP($smtp_server, 465, Swift_Connection_SMTP::ENC_SSL);
		  $connection->setUsername($smtp_user);
		  $connection->setPassword($smtp_password);
		  $mailer = new Swift($connection);  
		  
		  // Subject line
		  $message = new Swift_Message('Your FinalsClub password reset request');
		 
		  // Render message parts
		  $mailContext = array('reset_url'=>$reset_url, 'name'=>$user->getFullName());	// to be passed as parameters to the partials
		  $message->attach(new Swift_Message_Part($this->getPartial('forgotPasswordHtml', $mailContext), 'text/html'));		// HTML e-mail body
		  $message->attach(new Swift_Message_Part($this->getPartial('forgotPasswordPlain', $mailContext), 'text/plain'));	// Plaintext e-mail body
		 
		  // Set the 'To' and 'From' fields
		  $mailer->send($message, $user->getEmail(), $smtp_user);
		  $mailer->disconnect();
		}
		catch (Exception $e)
		{
		  	$this->logMessage($e->getMessage(), 'crit');	// Log the exception as critical
		  	$this->getUser()->setFlash('warning', 'Failed to initialize mailer!');		// alert the user e-mailing failed
		}
    }
    else
    	$this->setTemplate('new');	// if the form is invalid let them try again
 }

  /**
  * Actually handles the reset of the user's password. The action takes the reset request id
  * and token pair as request parameters and then puts them into the form to be passed back 
  * To the action when the form is posted. When the form is posted the password and confirm 
  * password fields are compared by the validator and then the action compares the token of
  * the database object that belongs to the request id parameter and the token parameter, if
  * these match the password is set to the one the user input and the reset request is deleted. 
  * 
  * @param $request
  * @return sfView::SUCCESS
  */
  public function executeReset($request) {
  	$this->user = new User();
  	$this->form = new ChangePasswordResetForm();
  	
  	if(!($request->isMethod('post'))) {		// if the request is NOT a POST our rid and token are in the request parameters
  		$rid = $request->getParameter('id');	
  		$token = $request->getParameter('token');
  	}
  	else {		// otherwise our rid and token are coming from the hidden form fields
  		$rid = $request->getParameter('password_change[rid]');
  		$token = $request->getParameter('password_change[token]');
  	}
  	
  	// make sure that the form fields for rid and token are populated
  	$this->form->setDefault('password_change[rid]', $rid);
  	$this->form->setDefault('password_change[token]', $token);
  	
	if($request->isMethod('post')) {
		// bind the form
 		$this->form->bind($request->getParameter('password_change'));
  		if($this->form->isValid()) {	// the validator here just compares password and confirm_password
  			// The form is valid now lets double check the request (id and token)
  			$rid = $this->form->getValue('rid');		// get the cleaned rid value
  			$token = $this->form->getValue('token');	// get the cleaned token value
  			
  			// make sure a reset request with the id == $rid exists
  			$reset_request = PasswordResetPeer::retrieveById($rid);
  	    	if(($reset_request instanceOf PasswordReset)) {		// request entry was found
  	   			$stored_expiration = $reset_request->getExpiredAt();	// get the expiration of the request
    			$link_expiration = strtotime($stored_expiration);		// convert the expiration from the database to a unix timestamp for comparison
    			$now = time();	// current unix timestamp 
				if($now < $link_expiration) {	// has the link expired?
					// if the link has not expired yet check the token from the form against the token stored in the database
  	        		$stored_token = $reset_request->getToken();
    				if(strcmp($token, $stored_token) === 0) {	// identical
  						$uid = $reset_request->getUserId();		// get the user id to lookup the user object
	 					$this->user = UserPeer::retrieveById($uid);		// retrieve user object
	 					$this->user->setMd5Password(md5($this->form->getValue('password')));	// set password
	 					$this->user->save();	// save record
	 					
	 					$reset_request->delete();	// delete the reset request record
	 					$this->getUser()->setFlash('success', 'Passwords matched and now you should be able to login with your new password');
	 					$this->redirect('site/home');
    				}
    				else {	// the tokens didn't match send them to submit a new request
    					$this->form = new PasswordResetForm();
    					$this->getUser()->setFlash('warning', 'Invalid token!');
    					$this->setTemplate('new');
    				}
  	    		}
  	    		else {	// the link has expired send them to submit a new request
  	    			$this->form = new PasswordResetForm();
    				$this->getUser()->setFlash('warning', 'The link you clicked has expired.');
    				$this->setTemplate('new');
  	    		}
  	    	}
  	    	else {	// request id was invalid 
    			$this->form = new PasswordResetForm();
    			$this->getUser()->setFlash('warning', 'Incomplete request!');
    			$this->setTemplate('new');
    		}
		}
	}
  }
  
}
